AWS API Gateway + Lambda + Slack App - 新建 slash command

接之前AWS Lex 创建 Slack Bot - Integrating Lex Bot with Slack,介绍怎么在 slack app 的基础上添加 slash command。

slash command 的配置页面需要一个 Request URL,这个 URL 就由 AWS API Gateway 来提供,API Gateway 调用 AWS Lambda 来完成具体的动作,而 Lambda 需要一个 slack token 来验证 slack app 的身份,才能够调用 slack api 来接收/回复信息,这个 token 由 AWS IAM 进行加密保障安全性。下面的教程分别介绍了怎么加密 token,编写 Lambda,配置 API Gateway,来完成一个 slash command 的创建。

Encrypt KMS Key

Step 1:IAM console 创建一个 KMS key,记录下 key-id,后面需要用到。
Step 2:slack apps 选择你的 app,然后在 Basic Information 下的 App Credentials 部分找到 Verification Token,记录下来,这就是我们需要加密的 token。

command-token.png

Step 3: 我们的目的是用 Step 1 创建的 key 来对 Step 2 记录的 token 进行加密。如果电脑已经装了 awscli,那么直接在命令行输入下面的命令即可。

1
$ aws kms encrypt --key-id <key-id> --plaintext <text>

其中 key-id 就是开始记录下的 kms key id,text 就是 verification token,把产生的 CiphertextBlob记录下来,在下一步 Lambda Configuration 里要用。

如果没有安装 aws-cli,OSX 系统直接用 brew 命令安装一下,最好不要用 pip,很大概率会出问题。

1
$ brew install awscli

装好后,aws --version 查看是否安装成功, aws configure 命令来配置账户信息,会要求输入

  • AWS Access Key ID: 在 aws console 的 My Security Credentials
  • AWS Secret Access Key: 现在必须创建一个 IAM user 才能得到,戳 create IAM user)
  • Default region name: 根据实际情况填,如果是 US East (N. Virginia),就填 us-east-1,注意不要在末尾加 abcd,us-east-1a 类似的格式会出错
  • Default output format: 可以不填

配置完正常进行加密即可。

Lambda Configuration

Lambda blueprint 选 slack-echo-command,创建 lambda function slashTest,这里要实现的是当用户 [user] 调用 /test 这个 [command] 时(之后会创建)并输入文本 [text] 时,返回 [user] invoked [command,e.g., /test] in [channel,eg., directmessage] with the following text: [text]"。代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
'use strict';
/*
This function handles a Slack slash command and echoes the details back to the user.
Follow these steps to configure the slash command in Slack:
1. Navigate to https://<your-team-domain>.slack.com/services/new
2. Search for and select "Slash Commands".
3. Enter a name for your command and click "Add Slash Command Integration".
4. Copy the token string from the integration settings and use it in the next section.
5. After you complete this blueprint, enter the provided API endpoint URL in the URL field.
To encrypt your secrets use the following steps:
1. Create or use an existing KMS Key - http://docs.aws.amazon.com/kms/latest/developerguide/create-keys.html
2. Click the "Enable Encryption Helpers" checkbox
3. Paste <COMMAND_TOKEN> into the kmsEncryptedToken environment variable and click encrypt
Follow these steps to complete the configuration of your command API endpoint
1. When completing the blueprint configuration select "Open" for security
on the "Configure triggers" page.
2. Enter a name for your execution role in the "Role name" field.
Your function's execution role needs kms:Decrypt permissions. We have
pre-selected the "KMS decryption permissions" policy template that will
automatically add these permissions.
3. Update the URL for your Slack slash command with the invocation URL for the
created API resource in the prod stage.
*/
const AWS = require('aws-sdk');
const qs = require('querystring');
const kmsEncryptedToken = process.env.kmsEncryptedToken;
let token;
function processEvent(event, callback) {
const params = qs.parse(event.body);
const requestToken = params.token;
if (requestToken !== token) {
console.error(`Request token (${requestToken}) does not match expected`);
return callback('Invalid request token');
}
const user = params.user_name;
const command = params.command;
const channel = params.channel_name;
const commandText = params.text;
callback(null, `${user} invoked ${command} in ${channel} with the following text: ${commandText}`);
}
exports.handler = (event, context, callback) => {
const done = (err, res) => callback(null, {
statusCode: err ? '400' : '200',
body: err ? (err.message || err) : JSON.stringify(res),
headers: {
'Content-Type': 'application/json',
},
});
if (token) {
// Container reuse, simply process the event with the key in memory
processEvent(event, done);
} else if (kmsEncryptedToken && kmsEncryptedToken !== '<kmsEncryptedToken>') {
const cipherText = { CiphertextBlob: new Buffer(kmsEncryptedToken, 'base64') };
const kms = new AWS.KMS();
kms.decrypt(cipherText, (err, data) => {
if (err) {
console.log('Decrypt error:', err);
return done(err);
}
token = data.Plaintext.toString('ascii');
processEvent(event, done);
});
} else {
done("Token has not been set.");
}
};

配置需要注意的是 Role 的选择,默认从 templates 里选 kmsDecrypt,不用修改,加个名字就好。在 Environment variables 里填写上一部分记录下的加密后的 token。但是!代码里请不要修改!!

role.png

API Gateway Configuration

关于基础的 API Gateway 教程,见 AWS API Gateway + Lambda 教程 - 生成随机数

aws console 页面的 Services -> Application Service -> API Gateway 下新建 API,然后在 Action 下拉框下 Create Resource,名称可以写 /test,然后继续 Create Method ,选 POST,Lambda Function 选择之前我们已经建好的 slashTest,保存后在新页面选择 Integration Request,新建一个 mapping template,如下:

mapping.png

Content-Type: application/x-www-form-urlencoded
Template: { "body": $input.json("$") }

之后部署,Actions -> Deploy API,记下完成页面显示的 Invoke URL

Slack App Configuration

回到 slack app 页面,选择左侧的 Slash Commands,新建一个 Command,Request URL 填写上一部分记录下来的地址,注意将 sub resource name 补充完整,这里是 /test。完成后记得 reinstall app

command.png

返回结果:

1
{"statusCode":"200","body":"\"sxu1 invoked /test in directmessage with the following text: hello world\"","headers":{"Content-Type":"application/json"}}

如果要返回 plain text,直接修改 lambda function,

1
2
3
4
5
6
7
8
exports.handler = (event, context, callback) => {
const done = (err, res) => callback(null, res); //{
// statusCode: err ? '400' : '200',
// body: err ? (err.message || err) : JSON.stringify(res),
// headers: {
// 'Content-Type': 'application/json',
// },
// });

test.png
徐阿衡 wechat
欢迎关注:徐阿衡的微信公众号
客官,打个赏呗~